package plantshop.model;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import plantshop.controller.moves.CustomerBuyMove;
import plantshop.controller.moves.CustomerFindGoalMove;
import plantshop.controller.moves.CustomerWalkMove;
import plantshop.controller.moves.DeleteCustomerMove;
import plantshop.controller.moves.Move;
import plantshop.model.exceptions.ImproperLengthException;
import plantshop.model.plants.Plant;


public class Customer implements Serializable, Comparable<Customer>{
	private static final long serialVersionUID = 1838795950251405511L;

	private static final Random rand = new Random();
	
	private static final int MIDDLE = 5;
	private static final int RANGE = 6;
	private static final int THRESHOLD = 75;

	public static int CUST_WIDTH;
	public static int CUST_HEIGHT;
	
	private static final int MOVES_IN_A_ROW = 50;
	private static final int NUM_HOLDS = 4;
	
	private static final int VALID = 1;
	private static final int CUSTOMER = 0;
	private static final int DISPLAY = -1;
	private static final int NO_DOMINANT = -1;
	
	public static final int SAD = -1;
	public static final int NEUTRAL = 0;
	public static final int HAPPY = 1;
	
	public static Location EXIT;
	public static Location ENTRANCE;
	
	public static double xscale, yscale;

	private Location goalLoc;
	private Location lastLoc;
	private Plant goalPlant;
	private int type;
	private Characteristics desiredPlant;
	private Location topLeft, bottomRight;
	private boolean leave;
	private int wandering;
	private Move lastMove;
	private boolean stuck;
	private int displayMoneySign;
	private int moneyNum;
	private int emotion; //-1 = sad, 0 = neutral, 1 = happy
	
	public static void setExit(Location loc){
		EXIT = loc;
	}
	public static void setEntrance(Location loc){
		ENTRANCE = loc;
	}
	
	public static void setXScale(double s){
		xscale = s;
		CUST_WIDTH = (int)(125 * xscale);
	}
	public static void setYScale(double s){
		yscale = s;
		CUST_HEIGHT = (int)(50 * yscale);
	}
	
	public boolean isLeaving(){
		return leave;
	}

	private Customer(int type, Characteristics desiredPlant){
		this.type = type;
		this.desiredPlant = desiredPlant;
		leave = false;
		wandering = 0;
		lastLoc = new Location(ENTRANCE.getX(), ENTRANCE.getY());
		stuck = false;
		emotion = 0;
	}

	public static Customer makeCustomer(){

		Characteristics characteristics = new Characteristics(MIDDLE+rand.nextInt(RANGE), 
				MIDDLE+rand.nextInt(RANGE),
				MIDDLE+rand.nextInt(RANGE),
				MIDDLE+rand.nextInt(RANGE),
				MIDDLE+rand.nextInt(RANGE));

		int type = rand.nextInt(3);

		return makeCustomer(type, characteristics);
	}

	public static Customer makeCustomer(int type, Characteristics characteristics)
	{
		Customer c = new Customer(type, characteristics);
		c.setLoc(ENTRANCE);
		return c;
	}

	public void setLoc(Location loc){
		if(loc != null)
		{
			topLeft = loc;
			bottomRight = new Location(topLeft.getX()+CUST_WIDTH, topLeft.getY()+CUST_HEIGHT);
		}
	}

	public boolean wantsToBuy(Plant p){
		//Only compares for closeness to desired plant
		if(p != null && p.available()){
			try {
				return p.getCharacteristics().getCloseness(desiredPlant) < THRESHOLD;
			} catch (ImproperLengthException e) {
				e.printStackTrace();
			}
		}
		return false;
	}

	public Move getNextMove(){
		//Decision making logic for customers
		Move move = null;
		if(goalLoc == null){
			if(desiredPlant.getDominant() == NO_DOMINANT)
				if(wandering%MOVES_IN_A_ROW == 1){
					wandering++;
					return new CustomerFindGoalMove(this);
				}
				else{
					return getWanderMove();
				}
		}
		if(leave){
			if(!topLeft.equals(EXIT))
				move = getWalkTowardsMove(EXIT);
			else
				move = deleteCustomer();
		} else if(goalLoc != null && !topLeft.equals(goalLoc)){
			move = getWalkTowardsMove(goalLoc);
		} else if(goalLoc != null && !leave){
			move = buy(goalPlant.getLoc());
			leave = true;
		} else
			move = getWanderMove();

		return move;
	}

	public Move getWalkTowardsMove(Location goal){
		//This is probably not what we want

		int x = goal.getX()-topLeft.getX();
		int y = goal.getY()-topLeft.getY();
		Map<Integer, Move> possible = new HashMap<Integer, Move>();
		int key = 0;
		
		int validityxplus, validityxminus, validityyplus, validityyminus;
		validityxplus = CollisionDetector.isValid(new Location(topLeft.getX() + 1, topLeft.getY()), this);
		validityxminus = CollisionDetector.isValid(new Location(topLeft.getX()-1, topLeft.getY()), this);
		validityyminus = CollisionDetector.isValid(new Location(topLeft.getX(), topLeft.getY()-1), this);
		validityyplus = CollisionDetector.isValid(new Location(topLeft.getX(), topLeft.getY()+1), this);

		if(x == 0)
		{
			if(y > 0)
			{
				if(validityyplus == DISPLAY)
				{
					lastMove = move(0, 1);
					stuck = true;
				}
			}
			else if(y < 0)
			{
				if(validityyminus == DISPLAY)
				{
					lastMove = move(0, -1);
					stuck = true;
				}
			}
		}
		else if(y == 0)
		{
			if(x > 0)
			{
				if(validityxplus == DISPLAY)
				{
					lastMove = move(1, 0);
					stuck = true;
				}
			}
			else if(x < 0)
			{
				if(validityxminus == DISPLAY)
				{
					lastMove = move(-1, 0);
					stuck = true;
				}
			}
		}

		if(!stuck)
		{
			if(x > 0){
				if(validityxplus == VALID && validityyplus != CUSTOMER){
					possible.put(key++, move(1,0));
				}else if(validityxplus == CUSTOMER){
					if(validityyplus == VALID)
						possible.put(key++, move(0,1));
				}
			}
			if(y > 0){
				if(validityyplus == VALID && validityxminus != CUSTOMER){
					possible.put(key++, move(0,1));
				}else if(validityyplus == CUSTOMER){
					if(validityxminus == VALID)
						possible.put(key++, move(-1,0));
				}
			}
			if(y < 0){
				if(validityyminus == VALID && validityxplus != CUSTOMER){
					possible.put(key++, move(0,-1));
				}else if(validityyminus == CUSTOMER){
					if(validityxplus == VALID)
						possible.put(key++, move(1,0));
				}
			}
			if(x < 0){
				if(validityxminus == VALID && validityyminus != CUSTOMER){
					possible.put(key++, move(-1,0));
				}else if(validityxminus == CUSTOMER){
					if(validityyminus == VALID)
						possible.put(key++, move(0,-1));
				}
			}

			if(!possible.isEmpty())
				lastMove = possible.get(rand.nextInt(possible.size()));
			else
				lastMove = move(0, 0);
			lastLoc.setX(topLeft.getX());
			lastLoc.setY(topLeft.getY());
			return lastMove;
		}
		else
		{
			if(y > 0)
			{
				if(validityyplus == VALID)
				{
					stuck = false;
					return lastMove;
				}
				else if(validityxplus == VALID)
					return move(1, 0);
			}
			else if(y < 0)
			{
				if(validityyminus == VALID)
				{
					stuck = false;
					return lastMove;
				}
				else if(validityxminus == VALID)
					return move(-1, 0);
			}
			else if(x > 0)
			{
				if(validityxplus == VALID)
				{
					stuck = false;
					return lastMove;
				}
				else if(validityyminus == VALID)
					return move(0, -1);
			}
			else if(x < 0)
			{
				if(validityxminus == VALID)
				{
					stuck = false;
					return lastMove;
				}
				else if(validityyplus == VALID)
					return move(0, 1);
			}

		}
		return move(0, 0);
	}

	public Move getWanderMove()
	{
		int key = 0;
		if(wandering%MOVES_IN_A_ROW == 0){
			Map<Integer, Move> possible = new HashMap<Integer, Move>();
			for(int i = 0; i < NUM_HOLDS; i++)
				possible.put(key++, move(0,0));
			if(CollisionDetector.isValid(new Location(topLeft.getX() + 1, topLeft.getY()), this) == VALID)
				possible.put(key++, move(1,0));
			if(CollisionDetector.isValid(new Location(topLeft.getX(), topLeft.getY() + 1), this) == VALID)
				possible.put(key++, move(0,1));
			if(CollisionDetector.isValid(new Location(topLeft.getX(), topLeft.getY() - 1), this) == VALID)
				possible.put(key++, move(0,-1));
			if(CollisionDetector.isValid(new Location(topLeft.getX() - 1, topLeft.getY()), this) == VALID)
				possible.put(key++, move(-1,0));
			if(!possible.isEmpty())
			{
				lastMove = possible.get(rand.nextInt(possible.size()));
			}
		}
		else
		{
			if(CollisionDetector.isValid(new Location(topLeft.getX() + ((CustomerWalkMove)lastMove).getX(), topLeft.getY() + ((CustomerWalkMove)lastMove).getY()), this) != 1)
			{
				Map<Integer, Move> possible = new HashMap<Integer, Move>();
				if(CollisionDetector.isValid(new Location(topLeft.getX() + 1, topLeft.getY()), this) == 1)
					possible.put(key++, move(1,0));
				if(CollisionDetector.isValid(new Location(topLeft.getX(), topLeft.getY() + 1), this) == 1)
					possible.put(key++, move(0,1));
				if(CollisionDetector.isValid(new Location(topLeft.getX(), topLeft.getY() - 1), this) == 1)
					possible.put(key++, move(0,-1));
				if(CollisionDetector.isValid(new Location(topLeft.getX() - 1, topLeft.getY()), this) == 1)
					possible.put(key++, move(-1,0));
				if(possible.size() > 0)
				{
					lastMove = possible.get(rand.nextInt(possible.size()));
				}
			}		
		}
		wandering++;
		return lastMove;
	}

	private Move move(int x, int y){
		return new CustomerWalkMove(this, x, y);
	}

	private Move buy(Location goal){
		return new CustomerBuyMove(goal, this);
	}

	public void walk(Location newLoc){
		int x = newLoc.getX() - topLeft.getX();
		int y = newLoc.getY() - topLeft.getY();

		topLeft = newLoc;
		bottomRight.setX(bottomRight.getX() + x);
		bottomRight.setY(bottomRight.getY() + y);
	}

	public void setGoalLoc(Location loc)
	{
		goalLoc = loc;
	}

	public void setGoalPlant(Plant plant)
	{
		goalPlant = plant;
	}

	public Plant getGoalPlant()
	{
		return goalPlant;
	}

	public Location getLoc(){
		return topLeft;
	}

	public void setLeaveTrue()
	{
		leave = true;
	}

	public Move deleteCustomer(){
		return new DeleteCustomerMove(this);
	}

	public Characteristics getDesiredPlant(){
		return desiredPlant;
	}

	public int getType()
	{
		return type;
	}
	
	@Override
	public int compareTo(Customer c) {
		int loc1 = topLeft.getY();
		int loc2 = c.topLeft.getY();
		return loc1-loc2;
	}
	
	public int getMoneyNum(){
		return moneyNum;
	}
	
	public void setMoneyNum(int num){
		this.moneyNum = num;
	}
	
	public int getDisplayMoneySign()
	{
		return displayMoneySign;
	}
	
	public void setDisplayMoneySign(int displayMoneySign)
	{
		this.displayMoneySign = displayMoneySign;
	}
	
	public int getEmotion()
	{
		return emotion;
	}
	
	public void setEmotion(int emotion)
	{
		this.emotion = emotion;
	}
	
	public void setStuck(boolean stuck)
	{
		this.stuck = stuck;
	}
	
	public boolean getStuck()
	{
		return stuck;
	}
	
	public Location getBottomRight()
	{
		return bottomRight;
	}
}
